#include "watchpoint.h"
#include "temu.h"
#include "expr.h"

#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>

void cpu_exec(uint32_t);

void display_reg();

/* We use the 'readline' library to provide more flexibility to read from stdin. */
char *rl_gets() {
    static char *line_read = NULL;

    if (line_read) {
        free(line_read);
        line_read = NULL;
    }

    line_read = readline("\33[1;32m(temu) \33[0m");

    if (line_read && *line_read) {
        add_history(line_read);
    }

    return line_read;
}

static int cmd_c(Params _, char *args) {
    cpu_exec(-1);
    return 0;
}

static int cmd_q(Params _, char *args) {
    return -1;
}

static int cmd_help(Params _, char *args);

static int cmd_info(Params _, char *args) {

    if (args == NULL) {
        printf("Please tell me what do you want, ok?\n");
        return 1;
    } else if (strcmp(&args[0], "ok") == 0) {
        printf("You need to reply me directly.\n");
        return 250;
    }

    if (*args == 'r') {
        display_reg();
    } else if (*args == 'w') {
        list_used_wps();
    }

    return 0;
}

static int cmd_ok(Params _, char *args) {
    printf("Are you SB?\n");
    return 250;
}

static int cmd_si(Params _, char *args) {
    if (args != NULL) {
        char *end;
        long int steps;
        steps = strtol(args, &end, 10);

        if (*end != '\0') {
            printf("Illegal parameter: [%s]\n", args);
            return 1;
        }
        cpu_exec(steps);
    } else {
        cpu_exec(1);
    }
    return 0;
}

static int cmd_p(Params pars, char *args) {
    bool s = true;
    uint32_t ans;
    char *format;
    uint8_t flag = 0;
    if (args != NULL) {
        ans = expr(args, &s);
        if (!s) {
            printf("Wrong format.\n");
        } else {
            int i;
            for (i = 0; i < pars.count; ++i) {
                if (strcmp(pars.params[i].str, "-x") == 0) {
                    flag |= P_HEX_OUTPUT;
                } else {
                    printf("\33[1;33m[Warning] \33[0;33mIllegal param: %s\33[0m\n", pars.params[i].str);
                }
            }

            format = flag & P_HEX_OUTPUT ? "-> 0x%x\n" : "-> %d\n";
            printf(format, ans);
        }
    } else {
        printf("Please tell me what do you want, ok?\n");
        return 1;
    }
    return 0;
}

static int cmd_x(Params _, char *args) {
    char *end, *exp;
    bool success = true;
    uint32_t val = 0, mem_val;
    if (args == NULL) {
        printf("\"x\" requires two parameters.\n");
        return 1;
    }
    long n = strtol(strtok(NULL, " "), &end, 10);
    if (*end != '\0') {
        printf("N should be a number. \n");
        return 2;
    }
    exp = strtok(NULL, "");

    val = expr(exp, &success);
    if (!success) {
        printf("Wrong format.\n");
        return 3;
    }
    printf("\33[1;34m n:[%ld],exp:[%s],val:[%d] \33[0m\n", n, exp, val);
    while (n > 0) {
        mem_val = mem_read(val & 0x7fffffff, 4);
        printf("[0x%08x] : [%08x]\n", val, mem_val);
        val += 4;
        n--;
    }

    return 0;
}

static int cmd_w(Params _, char *args) {
    char *exp;
    bool success;
    uint32_t value;
    if (args == NULL) {
        printf("Please input your expression.\n");
        return 1;
    }
    // 解析并求值
    exp = strtok(NULL, "");
    printf("\33[1;34m exp:[%s] \33[0m\n", exp);
    value = expr(exp,&success);
    if (!success) {
        printf("Can't calculate value of the EXP.\n");
        return 2;
    }
    // 保存到WP
    WP *wp = new_wp();
    strcpy(wp->exp, exp);
    wp->value = value;
    printf("No.%d wp is watching [%s]=[%d]...\n", wp->NO, wp->exp, wp->value);

    return 0;
}

static int cmd_d(Params _, char *args) {
    int no;
    char *end;
    if (args == NULL) {
        printf("Please input the num of wp you want to remove.\n");
        return 1;
    }
    no = (int) strtol(strtok(NULL, ""), &end, 10);
    if (*end != '\0') {
        printf("NO should be a number.\n");
        return 2;
    }
    free_wp_no(no);

    return 0;
}

static struct {
    char *name;
    char *description;

    int (*handler)(Params, char *);
} cmd_table[] = {
        {"ok",   "",                                                                               cmd_ok},
        {"help", "help [cmd]\tDisplay information about all supported commands",                   cmd_help},
        {"h",    "h [cmd]\tShorthand of 'help'",                                                   cmd_help},
        {"c",    "\t\tContinue the execution of the program",                                      cmd_c},
        {"q",    "\t\tExit TEMU",                                                                  cmd_q},
        {"info", "info [r|w]\tShow some info, \"r\" for register, \"w\" for watchpoint",           cmd_info},
        {"si",   "si [N]\tSingle step execution, or exec N(Default to 1) steps",                   cmd_si},
        {"p",    "p [-x] EXP\tCalculate the value of EXP, \"-x\" for hexadecimal output",          cmd_p},
        {"x",    "x N EXP\tStarting from the value of EXP, print N * 4Byte length",                cmd_x},
        {"w",    "w EXP\t\tPause program execution when the value of the expression EXPR changes", cmd_w},
        {"d",    "d NO\t\tDelete the watchpoint of number NO",                                     cmd_d},

        /* TODO: Add more commands */
};

#define NR_CMD (sizeof(cmd_table) / sizeof(cmd_table[0]))

static int cmd_help(Params _, char *args) {
    /* extract the first argument */
    char *arg = strtok(NULL, " ");
    char *format = "    %s   \t- %s\n";
    int i;
    bool valid;
    bool repeated[NR_CMD] = {};
    printf("\n");
    if (arg == NULL) {
        /* no argument given */
        for (i = 1; i < NR_CMD; i++) {
            printf(format, cmd_table[i].name, cmd_table[i].description);
        }
    } else {
        while (arg != NULL) {
            valid = false;
            for (i = 1; i < NR_CMD; i++) {
                if (strcmp(arg, cmd_table[i].name) == 0) {
                    if (repeated[i] == 0) printf(format, cmd_table[i].name, cmd_table[i].description);
                    else printf("Duplicated command: '%s'\n", cmd_table[i].name);
                    repeated[i] = 1;
                    valid = true;
                    break;
                }
            }
            if (!valid) printf("Unknown command '%s'\n", arg);
            arg = strtok(NULL, " ");
        }
    }
    printf("\n");
    return 0;
}

void ui_mainloop() {
    while (1) {
        char *str = rl_gets();
        char *str_end = str + strlen(str);
        Params params;

        /* extract the first token as the command */
        char *cmd = strtok(str, " ");
        if (cmd == NULL) { continue; }

        /* treat the remaining string as the arguments,
         * which may need further parsing
         */
        char *args = cmd + strlen(cmd) + 1;
        if (args >= str_end) {
            args = NULL;
        }

        int i;
        for (i = 0; i < NR_CMD; i++) {
            if (strcmp(cmd, cmd_table[i].name) == 0) {
                get_param(args, &params);
#ifdef DEBUG
                int j;
                for (j = 0; j < params.count; ++j) {
                    printf("\33[0;33mparam %d: %s\33[0m\n", j + 1, params.params[j].str);
                }
#endif
                if (cmd_table[i].handler(params, args) < 0) { return; }
                break;
            }
        }

        if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); }
    }
}
